You said:
#!/bin/bash
# =============================================================================
# NanoTrace Admin Dashboard - Critical Fixes
# Fixes the import errors and missing templates that prevent admin panel from working
# =============================================================================
set -e
echo "Applying critical fixes to Admin Dashboard..."
PROJECT_ROOT="/home/michal/NanoTrace"
cd $PROJECT_ROOT
source venv/bin/activate
# 1. Fix SQLAlchemy or_ import in user views
echo "Fixing SQLAlchemy import in users.py..."
cat > backend/app/admin/views/users.py << 'FIXED_USER_VIEWS'
from flask import render_template, request, jsonify, flash, redirect, url_for
from flask_login import current_user
from sqlalchemy import or_
from backend.app.admin import bp
from backend.app.admin.utils import admin_required, super_admin_required, log_admin_action
from backend.app.models.user import User
from backend.app import db
from datetime import datetime
@bp.route('/users')
@admin_required
def users():
"""User management page"""
log_admin_action("Accessed user management")
page = request.args.get('page', 1, type=int)
search = request.args.get('search', '')
role_filter = request.args.get('role', '')
query = User.query
if search:
query = query.filter(
or_(
User.username.contains(search),
User.email.contains(search)
)
)
if role_filter:
query = query.filter(User.role == role_filter)
users = query.paginate(
page=page, per_page=25, error_out=False
)
return render_template('users/list.html',
users=users,
search=search,
role_filter=role_filter)
@bp.route('/users/<int:user_id>')
@admin_required
def user_detail(user_id):
"""User detail page"""
from backend.app.models.certificate import Certificate
user = User.query.get_or_404(user_id)
log_admin_action("Viewed user details", "user", user_id)
# Get user's certificates with proper ordering
certificates = user.certificates.order_by(Certificate.created_at.desc()).limit(10).all()
# Get user's audit logs
from backend.app.models.admin_models import AuditLog
audit_logs = AuditLog.query.filter_by(user_id=user_id).order_by(AuditLog.timestamp.desc()).limit(20).all()
return render_template('users/detail.html',
user=user,
certificates=certificates,
audit_logs=audit_logs)
@bp.route('/users/<int:user_id>/toggle-status', methods=['POST'])
@admin_required
def toggle_user_status(user_id):
"""Toggle user active status"""
user = User.query.get_or_404(user_id)
user.is_active = not user.is_active
try:
db.session.commit()
action = "activated" if user.is_active else "deactivated"
log_admin_action(f"User {action}", "user", user_id)
return jsonify({
'success': True,
'message': f'User {action} successfully',
'is_active': user.is_active
})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 400
@bp.route('/users/create', methods=['GET', 'POST'])
@admin_required
def create_user():
"""Create new user"""
if request.method == 'POST':
data = request.get_json() if request.is_json else request.form
try:
user = User(
username=data['username'],
email=data['email'],
role=data.get('role', 'user'),
is_verified=data.get('is_verified', False) == 'true',
is_active=data.get('is_active', True) == 'true'
)
user.set_password(data['password'])
db.session.add(user)
db.session.commit()
log_admin_action("Created user", "user", user.id)
if request.is_json:
return jsonify({
'success': True,
'message': 'User created successfully',
'user_id': user.id
})
else:
flash('User created successfully', 'success')
return redirect(url_for('admin.user_detail', user_id=user.id))
except Exception as e:
db.session.rollback()
if request.is_json:
return jsonify({'success': False, 'error': str(e)}), 400
else:
flash(f'Error creating user: {str(e)}', 'error')
return render_template('users/create.html')
FIXED_USER_VIEWS
# 2. Fix SQLAlchemy or_ import in certificates views
echo "Fixing SQLAlchemy import in certificates.py..."
cat > backend/app/admin/views/certificates.py << 'FIXED_CERT_VIEWS'
from flask import render_template, request, jsonify, flash, redirect, url_for
from flask_login import current_user
from sqlalchemy import or_
from backend.app.admin import bp
from backend.app.admin.utils import admin_required, log_admin_action
from backend.app.models.certificate import Certificate
from backend.app.models.user import User
from backend.app import db
from datetime import datetime, timedelta
@bp.route('/certificates/<int:cert_id>')
@admin_required
def certificate_detail(cert_id):
"""Certificate detail page"""
cert = Certificate.query.get_or_404(cert_id)
log_admin_action("Viewed certificate details", "certificate", cert_id)
return render_template('certificates/detail.html', cert=cert)
@bp.route('/certificates')
@admin_required
def certificates():
"""Certificate management page"""
log_admin_action("Accessed certificate management")
page = request.args.get('page', 1, type=int)
status_filter = request.args.get('status', '')
search = request.args.get('search', '')
query = Certificate.query
if status_filter:
query = query.filter(Certificate.status == status_filter)
if search:
query = query.join(User).filter(
or_(
Certificate.product_name.contains(search),
Certificate.material_type.contains(search),
User.username.contains(search)
)
)
certificates = query.order_by(
Certificate.created_at.desc()
).paginate(page=page, per_page=25, error_out=False)
# Get statistics
stats = {
'total': Certificate.query.count(),
'pending': Certificate.query.filter_by(status='pending').count(),
'approved': Certificate.query.filter_by(status='approved').count(),
'rejected': Certificate.query.filter_by(status='rejected').count(),
'expired': Certificate.query.filter(
Certificate.expiry_date < datetime.utcnow()
).count() if hasattr(Certificate, 'expiry_date') else 0
}
return render_template('certificates/list.html',
certificates=certificates,
stats=stats,
status_filter=status_filter,
search=search)
@bp.route('/certificates/<int:cert_id>/approve', methods=['POST'])
@admin_required
def approve_certificate(cert_id):
"""Approve certificate"""
certificate = Certificate.query.get_or_404(cert_id)
if certificate.status != 'pending':
return jsonify({
'success': False,
'error': 'Certificate is not pending approval'
}), 400
try:
certificate.status = 'approved'
certificate.approved_at = datetime.utcnow()
certificate.approved_by = current_user.id
# Generate certificate ID if method exists
if hasattr(certificate, 'generate_certificate_id') and not certificate.certificate_id:
certificate.generate_certificate_id()
db.session.commit()
log_admin_action("Approved certificate", "certificate", cert_id)
return jsonify({
'success': True,
'message': 'Certificate approved successfully',
'certificate_id': getattr(certificate, 'certificate_id', 'N/A')
})
except Exception as e:
db.session.rollback()
return jsonify({'success': False, 'error': str(e)}), 400
@bp.route('/certificates/pending')
@admin_required
def pending_certificates():
"""Get pending certificates for approval queue"""
certificates = Certificate.query.filter_by(
status='pending'
).order_by(Certificate.created_at.asc()).all()
return jsonify([{
'id': cert.id,
'product_name': cert.product_name,
'material_type': cert.material_type,
'supplier': cert.user.username,
'created_at': cert.created_at.isoformat(),
'days_pending': (datetime.utcnow() - cert.created_at).days
} for cert in certificates])
FIXED_CERT_VIEWS
# 3. Create missing templates
echo "Creating missing admin templates..."
# User detail template
cat > backend/app/admin/templates/users/detail.html << 'USER_DETAIL_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}User Detail{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200 mb-6">
<h2 class="text-xl font-semibold mb-4">User: {{ user.username }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<h3 class="font-semibold text-gray-900 mb-2">Basic Information</h3>
<div class="space-y-2">
<p><strong>Username:</strong> {{ user.username }}</p>
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Role:</strong>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ 'bg-purple-100 text-purple-800' if user.role == 'super_admin'
else 'bg-blue-100 text-blue-800' if user.role == 'admin'
else 'bg-gray-100 text-gray-800' }}">
{{ user.role.replace('_', ' ').title() }}
</span>
</p>
<p><strong>Status:</strong>
<span class="inline-flex items-center">
<span class="status-indicator status-{{ 'healthy' if user.is_active else 'error' }}"></span>
{{ 'Active' if user.is_active else 'Inactive' }}
{% if user.is_verified %}
<i class="fas fa-check-circle text-green-500 ml-1" title="Verified"></i>
{% endif %}
</span>
</p>
<p><strong>Joined:</strong> {{ user.created_at.strftime('%Y-%m-%d %H:%M') if user.created_at else 'N/A' }}</p>
</div>
</div>
<div>
<h3 class="font-semibold text-gray-900 mb-2">Actions</h3>
<div class="space-y-2">
<button onclick="toggleUserStatus({{ user.id }}, {{ user.is_active | tojson }})"
class="block w-full text-left px-4 py-2 bg-{{ 'red' if user.is_active else 'green' }}-500 text-white rounded hover:bg-{{ 'red' if user.is_active else 'green' }}-600">
{{ 'Deactivate User' if user.is_active else 'Activate User' }}
</button>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Recent Certificates ({{ certificates | length }})</h3>
<div class="space-y-3">
{% for cert in certificates %}
<div class="p-3 bg-gray-50 rounded-lg">
<div class="flex justify-between items-start">
<div>
<p class="font-medium text-gray-900">{{ cert.product_name }}</p>
<p class="text-sm text-gray-500">{{ cert.material_type }}</p>
</div>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ 'bg-green-100 text-green-800' if cert.status == 'approved'
else 'bg-yellow-100 text-yellow-800' if cert.status == 'pending'
else 'bg-red-100 text-red-800' }}">
{{ cert.status.title() }}
</span>
</div>
<p class="text-xs text-gray-500 mt-1">{{ cert.created_at.strftime('%Y-%m-%d') if cert.created_at else 'N/A' }}</p>
</div>
{% else %}
<p class="text-gray-500 text-center py-4">No certificates found.</p>
{% endfor %}
</div>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Recent Activity ({{ audit_logs | length }})</h3>
<div class="space-y-3">
{% for log in audit_logs %}
<div class="p-3 bg-gray-50 rounded-lg">
<p class="text-sm text-gray-900">{{ log.action }}</p>
<p class="text-xs text-gray-500">{{ log.timestamp.strftime('%Y-%m-%d %H:%M') if log.timestamp else 'N/A' }}</p>
</div>
{% else %}
<p class="text-gray-500 text-center py-4">No recent activity.</p>
{% endfor %}
</div>
</div>
</div>
<script>
function toggleUserStatus(userId, isActive) {
const action = isActive ? 'deactivate' : 'activate';
if (confirm(
Are you sure you want to ${action} this user?)) {
showLoading();
fetch(/admin/users/${userId}/toggle-status, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
hideLoading();
alert('Error: ' + error.message);
});
}
}
</script>
{% endblock %}
USER_DETAIL_TEMPLATE
# User create template
cat > backend/app/admin/templates/users/create.html << 'USER_CREATE_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}Create User{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h2 class="text-xl font-semibold text-gray-900 mb-6">Create New User</h2>
<form method="post" class="space-y-4 max-w-md">
<div>
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
<input type="text" id="username" name="username"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required>
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="email" name="email"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" id="password" name="password"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required>
</div>
<div>
<label for="role" class="block text-sm font-medium text-gray-700 mb-1">Role</label>
<select id="role" name="role"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="super_admin">Super Admin</option>
</select>
</div>
<div class="flex items-center">
<input type="checkbox" id="is_verified" name="is_verified" value="true" class="mr-2">
<label for="is_verified" class="text-sm text-gray-700">Verified Account</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="is_active" name="is_active" value="true" checked class="mr-2">
<label for="is_active" class="text-sm text-gray-700">Active Account</label>
</div>
<div class="flex space-x-4">
<button type="submit"
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Create User
</button>
<a href="{{ url_for('admin.users') }}"
class="px-6 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-colors">
Cancel
</a>
</div>
</form>
</div>
{% endblock %}
USER_CREATE_TEMPLATE
# Certificate detail template
cat > backend/app/admin/templates/certificates/detail.html << 'CERT_DETAIL_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}Certificate Detail{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-semibold text-gray-900">Certificate: {{ cert.product_name }}</h2>
<div class="flex space-x-2">
{% if cert.status == 'pending' %}
<button onclick="approveCertificate({{ cert.id }})"
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600">
<i class="fas fa-check mr-2"></i>Approve
</button>
<button onclick="rejectCertificate({{ cert.id }})"
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">
<i class="fas fa-times mr-2"></i>Reject
</button>
{% endif %}
<a href="{{ url_for('admin.certificates') }}"
class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400">
<i class="fas fa-arrow-left mr-2"></i>Back to List
</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<h3 class="font-semibold text-gray-900">Certificate Information</h3>
<div class="space-y-2">
<p><strong>Product Name:</strong> {{ cert.product_name }}</p>
<p><strong>Material Type:</strong> {{ cert.material_type }}</p>
<p><strong>Status:</strong>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ 'bg-green-100 text-green-800' if cert.status == 'approved'
else 'bg-yellow-100 text-yellow-800' if cert.status == 'pending'
else 'bg-red-100 text-red-800' }}">
{{ cert.status.title() }}
</span>
</p>
<p><strong>Created:</strong> {{ cert.created_at.strftime('%Y-%m-%d %H:%M') if cert.created_at else 'N/A' }}</p>
{% if cert.certificate_id %}
<p><strong>Certificate ID:</strong>
<code class="bg-gray-100 px-2 py-1 rounded text-sm">{{ cert.certificate_id }}</code>
</p>
{% endif %}
{% if cert.batch_number is defined and cert.batch_number %}
<p><strong>Batch Number:</strong> {{ cert.batch_number }}</p>
{% endif %}
{% if cert.quantity is defined and cert.quantity %}
<p><strong>Quantity:</strong> {{ cert.quantity }}</p>
{% endif %}
</div>
</div>
<div class="space-y-4">
<h3 class="font-semibold text-gray-900">Supplier Information</h3>
<div class="space-y-2">
<p><strong>Supplier:</strong> {{ cert.user.username }}</p>
<p><strong>Email:</strong> {{ cert.user.email }}</p>
<p><strong>User ID:</strong> {{ cert.user.id }}</p>
</div>
{% if cert.status != 'pending' %}
<div class="pt-4 border-t border-gray-200">
<h3 class="font-semibold text-gray-900 mb-2">Approval Information</h3>
<div class="space-y-2">
{% if cert.approved_at %}
<p><strong>Approved At:</strong> {{ cert.approved_at.strftime('%Y-%m-%d %H:%M') }}</p>
{% if cert.approver %}
<p><strong>Approved By:</strong> {{ cert.approver.username }}</p>
{% endif %}
{% endif %}
{% if cert.rejected_at %}
<p><strong>Rejected At:</strong> {{ cert.rejected_at.strftime('%Y-%m-%d %H:%M') }}</p>
{% if cert.rejecter %}
<p><strong>Rejected By:</strong> {{ cert.rejecter.username }}</p>
{% endif %}
{% if cert.rejection_reason %}
<p><strong>Rejection Reason:</strong></p>
<div class="bg-red-50 border border-red-200 rounded p-3 text-sm">
{{ cert.rejection_reason }}
</div>
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% if cert.description is defined and cert.description %}
<div class="mt-6 pt-6 border-t border-gray-200">
<h3 class="font-semibold text-gray-900 mb-2">Description</h3>
<div class="bg-gray-50 border border-gray-200 rounded p-4">
{{ cert.description }}
</div>
</div>
{% endif %}
{% if cert.qr_code_path is defined and cert.qr_code_path %}
<div class="mt-6 pt-6 border-t border-gray-200">
<h3 class="font-semibold text-gray-900 mb-2">QR Code</h3>
<img src="{{ cert.qr_code_path }}" alt="Certificate QR Code" class="w-32 h-32">
</div>
{% endif %}
</div>
<script>
function approveCertificate(certId) {
if (confirm('Are you sure you want to approve this certificate?')) {
showLoading();
fetch(/admin/certificates/${certId}/approve, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
alert('Certificate approved successfully');
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
hideLoading();
alert('Error: ' + error.message);
});
}
}
function rejectCertificate(certId) {
const reason = prompt('Please provide a reason for rejection:');
if (reason) {
showLoading();
fetch(/admin/certificates/${certId}/reject, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ reason: reason })
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
alert('Certificate rejected');
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
hideLoading();
alert('Error: ' + error.message);
});
}
}
</script>
{% endblock %}
CERT_DETAIL_TEMPLATE
# Certificate list template
cat > backend/app/admin/templates/certificates/list.html << 'CERT_LIST_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}Certificates{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200 mb-6">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<h2 class="text-xl font-semibold text-gray-900 mb-4 sm:mb-0">Certificate Management</h2>
<div class="flex flex-col sm:flex-row gap-4">
<div class="relative">
<input type="text" id="cert-search" placeholder="Search certificates..."
value="{{ search }}"
class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<select id="status-filter" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">All Status</option>
<option value="pending" {{ 'selected' if status_filter == 'pending' }}>Pending</option>
<option value="approved" {{ 'selected' if status_filter == 'approved' }}>Approved</option>
<option value="rejected" {{ 'selected' if status_filter == 'rejected' }}>Rejected</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4 mt-6">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-certificate text-blue-600 text-xl mr-3"></i>
<div>
<p class="text-sm text-blue-600">Total</p>
<p class="text-2xl font-bold text-blue-900">{{ stats.total }}</p>
</div>
</div>
</div>
<div class="bg-yellow-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-clock text-yellow-600 text-xl mr-3"></i>
<div>
<p class="text-sm text-yellow-600">Pending</p>
<p class="text-2xl font-bold text-yellow-900">{{ stats.pending }}</p>
</div>
</div>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-check text-green-600 text-xl mr-3"></i>
<div>
<p class="text-sm text-green-600">Approved</p>
<p class="text-2xl font-bold text-green-900">{{ stats.approved }}</p>
</div>
</div>
</div>
<div class="bg-red-50 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-times text-red-600 text-xl mr-3"></i>
<div>
<p class="text-sm text-red-600">Rejected</p>
<p class="text-2xl font-bold text-red-900">{{ stats.rejected }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Product</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Supplier</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for cert in certificates.items %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<div>
<div class="text-sm font-medium text-gray-900">{{ cert.product_name }}</div>
<div class="text-sm text-gray-500">{{ cert.material_type }}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900">{{ cert.user.username }}</div>
<div class="text-sm text-gray-500">{{ cert.user.email }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{{ 'bg-green-100 text-green-800' if cert.status == 'approved'
else 'bg-yellow-100 text-yellow-800' if cert.status == 'pending'
else 'bg-red-100 text-red-800' }}">
{{ cert.status.title() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ cert.created_at.strftime('%Y-%m-%d') if cert.created_at else 'N/A' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{% if cert.status == 'pending' %}
<button onclick="approveCertificate({{ cert.id }})"
class="text-green-600 hover:text-green-900 mr-3">Approve</button>
{% endif %}
<a href="/admin/certificates/{{ cert.id }}"
class="text-blue-600 hover:text-blue-900">View</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if certificates.pages > 1 %}
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
<div class="flex-1 flex justify-between sm:hidden">
{% if certificates.has_prev %}
<a href="{{ url_for('admin.certificates', page=certificates.prev_num, search=search, status=status_filter) }}"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">Previous</a>
{% endif %}
{% if certificates.has_next %}
<a href="{{ url_for('admin.certificates', page=certificates.next_num, search=search, status=status_filter) }}"
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">Next</a>
{% endif %}
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
Showing <span class="font-medium">{{ certificates.per_page * (certificates.page - 1) + 1 }}</span>
to <span class="font-medium">{{ certificates.per_page * certificates.page if certificates.has_next else certificates.total }}</span>
of <span class="font-medium">{{ certificates.total }}</span> results
</p>
</div>
<div>
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
{% for page_num in certificates.iter_pages() %}
{% if page_num %}
{% if page_num != certificates.page %}
<a href="{{ url_for('admin.certificates', page=page_num, search=search, status=status_filter) }}"
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">{{ page_num }}</a>
{% else %}
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600">{{ page_num }}</span>
{% endif %}
{% else %}
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">...</span>
{% endif %}
{% endfor %}
</nav>
</div>
</div>
</div>
{% endif %}
</div>
<script>
function approveCertificate(certId) {
if (confirm('Are you sure you want to approve this certificate?')) {
showLoading();
fetch(/admin/certificates/${certId}/approve, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
alert('Certificate approved successfully');
location.reload();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
hideLoading();
alert('Error: ' + error.message);
});
}
}
</script>
{% endblock %}
CERT_LIST_TEMPLATE
# Blockchain status template
cat > backend/app/admin/templates/blockchain/status.html << 'BLOCKCHAIN_STATUS_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}Blockchain Status{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200 mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-900">Hyperledger Fabric Network</h2>
<div class="flex space-x-2">
<button id="start-network" class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600">
<i class="fas fa-play mr-2"></i>Start Network
</button>
<button id="stop-network" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">
<i class="fas fa-stop mr-2"></i>Stop Network
</button>
<button onclick="location.reload()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fas fa-sync-alt mr-2"></i>Refresh
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for name, status in fabric_status.items() %}
<div class="bg-gray-50 p-4 rounded-lg border">
<div class="flex items-center justify-between mb-2">
<h3 class="font-medium text-gray-900">{{ name }}</h3>
<span class="status-indicator status-{{ 'healthy' if status.healthy else 'error' }}"></span>
</div>
<div class="space-y-1">
<p class="text-sm text-gray-600">Status:
<span class="font-medium {{ 'text-green-600' if status.healthy else 'text-red-600' }}">
{{ status.status }}
</span>
</p>
<p class="text-sm text-gray-600">Health:
<span class="font-medium {{ 'text-green-600' if status.healthy else 'text-red-600' }}">
{{ 'Healthy' if status.healthy else 'Unhealthy' }}
</span>
</p>
</div>
</div>
{% else %}
<div class="col-span-full text-center py-8">
<i class="fas fa-exclamation-triangle text-yellow-500 text-3xl mb-4"></i>
<p class="text-gray-600">No Fabric containers found or Docker not accessible.</p>
</div>
{% endfor %}
</div>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Network Information</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-blue-50 p-4 rounded-lg">
<h4 class="font-medium text-blue-900 mb-2">Peer Node</h4>
<p class="text-sm text-blue-700">peer0.org1.nanotrace.com</p>
<p class="text-xs text-blue-600 mt-1">Handles transactions and maintains ledger</p>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<h4 class="font-medium text-purple-900 mb-2">Orderer Node</h4>
<p class="text-sm text-purple-700">orderer.nanotrace.com</p>
<p class="text-xs text-purple-600 mt-1">Orders transactions into blocks</p>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<h4 class="font-medium text-green-900 mb-2">Certificate Authority</h4>
<p class="text-sm text-green-700">ca.org1.nanotrace.com</p>
<p class="text-xs text-green-600 mt-1">Manages digital certificates</p>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('start-network').addEventListener('click', function() {
controlFabricNetwork('start');
});
document.getElementById('stop-network').addEventListener('click', function() {
controlFabricNetwork('stop');
});
});
function controlFabricNetwork(action) {
if (!confirm(Are you sure you want to ${action} the Fabric network?)) {
return;
}
showLoading();
fetch(/admin/blockchain/${action}-network, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
alert(Network ${action} completed successfully);
setTimeout(() => location.reload(), 2000);
} else {
alert(Failed to ${action} network: ${data.error});
}
})
.catch(error => {
hideLoading();
alert(Error: ${error.message});
});
}
</script>
{% endblock %}
BLOCKCHAIN_STATUS_TEMPLATE
# System overview template
cat > backend/app/admin/templates/system/overview.html << 'SYSTEM_OVERVIEW_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}System Overview{% endblock %}
{% block content %}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<!-- System Statistics -->
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">System Statistics</h3>
<div class="space-y-4">
{% if system_stats.cpu %}
<div>
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>CPU Usage</span>
<span>{{ "%.1f" | format(system_stats.cpu.percent) }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: {{ system_stats.cpu.percent }}%"></div>
</div>
<p class="text-xs text-gray-500 mt-1">{{ system_stats.cpu.count }} cores available</p>
</div>
{% endif %}
{% if system_stats.memory %}
<div>
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Memory Usage</span>
<span>{{ "%.1f" | format(system_stats.memory.percent) }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-green-600 h-2 rounded-full" style="width: {{ system_stats.memory.percent }}%"></div>
</div>
<p class="text-xs text-gray-500 mt-1">{{ (system_stats.memory.used / 1024**3) | round(1) }} GB / {{ (system_stats.memory.total / 1024**3) | round(1) }} GB</p>
</div>
{% endif %}
{% if system_stats.disk %}
<div>
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Disk Usage</span>
<span>{{ "%.1f" | format(system_stats.disk.percent) }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-orange-600 h-2 rounded-full" style="width: {{ system_stats.disk.percent }}%"></div>
</div>
<p class="text-xs text-gray-500 mt-1">{{ (system_stats.disk.used / 1024**3) | round(1) }} GB / {{ (system_stats.disk.total / 1024**3) | round(1) }} GB</p>
</div>
{% endif %}
</div>
</div>
<!-- System Configuration -->
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">System Configuration</h3>
<div class="space-y-2 max-h-64 overflow-y-auto">
{% for key, value in config_dict.items() %}
<div class="flex justify-between py-2 border-b border-gray-100">
<span class="text-sm font-medium text-gray-700">{{ key }}</span>
<span class="text-sm text-gray-600 truncate ml-2" title="{{ value }}">{{ value[:50] }}{% if value|length > 50 %}...{% endif %}</span>
</div>
{% else %}
<p class="text-gray-500 text-center py-4">No configuration settings found.</p>
{% endfor %}
</div>
</div>
</div>
<!-- Disk Usage by Directory -->
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200 mb-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Disk Usage by Directory</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% for directory, usage in disk_usage.items() %}
{% if usage %}
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium text-gray-900 mb-2">{{ directory }}</h4>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">Total:</span>
<span class="font-medium">{{ usage.total_formatted }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Used:</span>
<span class="font-medium">{{ usage.used_formatted }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">Free:</span>
<span class="font-medium">{{ usage.free_formatted }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mt-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: {{ (usage.used / usage.total * 100) }}%"></div>
</div>
</div>
</div>
{% else %}
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium text-gray-900 mb-2">{{ directory }}</h4>
<p class="text-sm text-gray-500">Directory not accessible</p>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<!-- Raw System Data (for debugging) -->
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Raw System Data</h3>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<h4 class="font-medium text-gray-700 mb-2">System Stats</h4>
<pre class="bg-gray-50 p-4 rounded text-xs overflow-x-auto">{{ system_stats | tojson(indent=2) }}</pre>
</div>
<div>
<h4 class="font-medium text-gray-700 mb-2">Configuration</h4>
<pre class="bg-gray-50 p-4 rounded text-xs overflow-x-auto">{{ config_dict | tojson(indent=2) }}</pre>
</div>
</div>
</div>
{% endblock %}
SYSTEM_OVERVIEW_TEMPLATE
# System services template
cat > backend/app/admin/templates/system/services.html << 'SYSTEM_SERVICES_TEMPLATE'
{% extends "admin_base.html" %}
{% block page_title %}System Services{% endblock %}
{% block content %}
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200 mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-900">System Services Status</h2>
<button onclick="location.reload()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fas fa-sync-alt mr-2"></i>Refresh Status
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for name, status in services.items() %}
<div class="bg-gray-50 p-4 rounded-lg border">
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium text-gray-900">{{ name }}</h3>
<p class="text-sm text-gray-600">System Service</p>
</div>
<div class="flex items-center">
<span class="status-indicator status-{{ 'healthy' if status.active else 'error' }}"></span>
<span class="ml-2 text-sm font-medium {{ 'text-green-600' if status.active else 'text-red-600' }}">
{{ status.status }}
</span>
</div>
</div>
{% if status.error %}
<div class="mt-2 p-2 bg-red-50 border border-red-200 rounded text-xs text-red-700">
Error: {{ status.error }}
</div>
{% endif %}
</div>
{% else %}
<div class="col-span-full text-center py-8">
<i class="fas fa-exclamation-triangle text-yellow-500 text-3xl mb-4"></i>
<p class="text-gray-600">No service information available.</p>
</div>
{% endfor %}
</div>
</div>
<div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Service Descriptions</h3>
<div class="space-y-3">
<div class="border-l-4 border-blue-500 pl-4">
<h4 class="font-medium text-gray-900">PostgreSQL</h4>
<p class="text-sm text-gray-600">Database server for storing application data, user accounts, and certificates.</p>
</div>
<div class="border-l-4 border-red-500 pl-4">
<h4 class="font-medium text-gray-900">Redis Server</h4>
<p class="text-sm text-gray-600">In-memory data structure store used for caching and session management.</p>
</div>
<div class="border-l-4 border-green-500 pl-4">
<h4 class="font-medium text-gray-900">Nginx</h4>
<p class="text-sm text-gray-600">Web server and reverse proxy for serving the application and static files.</p>
</div>
<div class="border-l-4 border-purple-500 pl-4">
<h4 class="font-medium text-gray-900">NanoTrace</h4>
<p class="text-sm text-gray-600">Main Flask application service for the NanoTrace platform.</p>
</div>
</div>
</div>
{% endblock %}
SYSTEM_SERVICES_TEMPLATE
# 4. Run preflight checks
echo "Running preflight checks..."
# Check if app can load
python3 << 'PREFLIGHT_CHECK'
import sys
try:
from backend.app import create_app
app = create_app()
print("✓ App loads successfully")
except Exception as e:
print(f"✗ App loading failed: {e}")
sys.exit(1)
PREFLIGHT_CHECK
# Check Alembic environment
export FLASK_APP=backend.app:create_app
if flask db current &>/dev/null; then
echo "✓ Alembic environment OK"
else
echo "⚠ Alembic environment needs initialization"
fi
echo ""
echo "🎯 Critical fixes applied successfully!"
echo ""
echo "Fixed Issues:"
echo " ✓ SQLAlchemy or_ import added to user and certificate views"
echo " ✓ Certificate ordering fixed to use proper column expression"
echo " ✓ All missing templates created:"
echo " - users/detail.html"
echo " - users/create.html"
echo " - certificates/detail.html"
echo " - certificates/list.html"
echo " - blockchain/status.html"
echo " - system/overview.html"
echo " - system/services.html"
echo ""
echo "Next steps:"
echo "1. Run: ./integrate_admin.sh"
echo "2. Create admin user: python scripts/create_admin.py"
echo "3. Restart your Flask application"
echo "4. Access admin at: http://localhost:5000/admin/dashboard"
echo ""
echo "All admin routes should now load without 500 errors!"